/*
 * =====================================================================================
 *
 *       Filename:  chated_redis_adapter.cc
 *
 *    Description:  declearation for recent friends redsi adapter.
 *
 *        Version:  1.0
 *        Created:  2013年05月15日 14时48分40秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Wu Yuxin (wyx, Hin)
 *        Company:  www.Qihoo.net
 *
 * =====================================================================================
 */

#include "contact_redis.h"
#include <stdlib.h>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include "redis_executor/scoped_reply.h"
#include "base/logging.h"

#define _INVALID_UID (-1)

namespace imserver {

ContactRedis::ContactRedis(RedisLocator *locator) : redis_(locator) {
  LOG_INFO("ContactRedis constructor.");
}

int ContactRedis::AddContact(const int64_t sender, const int64_t receiver, const time_t timestamp) {
  LOG_INFO("[ContactRedis::AddContact] begin.sender=" << sender << " receiver=" << receiver << " timestamp=" << timestamp);
  // Save recent contacts, to take into consideration selecting and updating contacts list
  // and other operations. So we use the Sorted Sets, because the ZADD command can update
  // the score if the specifically key is exists.
  redisContext * context = redis_.AllocContext("");
  if (NULL == context) {
    LOG_ERROR("AllocContext failed.");
    return -2;
  }

  // Recent contacts are contact of both of sender & receiver
  // ZADD im:contacts:sender score receiver
  // ZADD im:contacts:receiver score sender
  if (REDIS_OK != redisAppendCommand(context, "ZADD im:contacts:%lld %lld %lld", sender, timestamp, receiver) ||
      REDIS_OK != redisAppendCommand(context, "ZADD im:contacts:%lld %lld %lld", receiver, timestamp, sender)) {
    LOG_ERROR("redisAppendCommand failed.");
    redis_.ReleaseContext("", context, false);
    return -1;
  }

  // check the result.
  redisReply *r = NULL;
  int i = 2;
  while (i-- > 0) {
    r = NULL;
    if (REDIS_OK != redisGetReply(context, (void**)&r) || NULL == r) {
      LOG_ERROR("redisGetReply failed.");
      redis_.ReleaseContext("", context, false);
      return -1;
    }
    freeReplyObject(r);
  }

  redis_.ReleaseContext("", context, true);
  return 0;
}

int ContactRedis::RemoveContact(const int64_t sender, const int64_t receiver) {
  LOG_INFO("[ContactRedis::RemoveContact] begin.sender=" << sender << " receiver=" << receiver);
  std::string str = boost::str(boost::format("ZREM im:contacts:%1% %2%") % sender % receiver);
  LOG_INFO("Remove Contact Redis-String is " << str.c_str());
  ScopedReply reply(redis_.Execute("", str.c_str()));

  if (!(reply && reply->type == REDIS_REPLY_INTEGER)) {
    LOG_ERROR("Remove Contact Failed.");
    return -1;
  }
  return 0;
}

int ContactRedis::GetContacts(RecentContactList& contacts, const int64_t uid, const int32_t offset, const int32_t count) {
  LOG_INFO("[ContactRedis::GetContacts] begin. uid=" << uid << " offset=" << offset);
  redisContext * context = redis_.AllocContext("");
  if (!context) {
    LOG_ERROR("GetContacts error.(alloc context failed.)");
    return -2;
  }

  {
    // ZREVRANGEBYSCORE im:contacts:uid +inf -inf WITHSCORE LIMIT offset limit
    std::string str = boost::str(boost::format("ZREVRANGEBYSCORE im:contacts:%1% +inf -inf WITHSCORES LIMIT %2% %3%") % uid % offset % count);
    std::string str2= boost::str(boost::format("HGETALL im:contacts:remark.%1%") % uid);
    //ScopedReply reply(redis_.Execute("", str.c_str()));
    if (0!=redisAppendCommand(context, str.c_str()) || 0!=redisAppendCommand(context, str2.c_str())) {
      LOG_ERROR("GetContacts error.(append redis command failed) - " << str << ";" << str2);
      redis_.ReleaseContext("", context, false);
      return -1;
    }
  }

  redisReply *reply = NULL;
  if (0 != redisGetReply(context, (void**)&reply) || !(reply && reply->type == REDIS_REPLY_ARRAY)) {
    redis_.ReleaseContext("", context, false);
    return -1;
  }

  int result = 0;
  redisReply *elem = NULL;
  // get all contacts.
  for (size_t i=0; i<reply->elements; i += 2) {
    elem = reply->element[i];
    LOG_DEBUG("GetContacts redis str " << elem->str);
    int64_t uid = (int64_t)atoll(elem->str);
    if (_INVALID_UID == uid) {
      LOG_DEBUG("Get All Contacts.");
      result = 1;
      continue;
    }

    contacts.push_back(RecentContact());
    RecentContact& rc  = contacts.back();
    rc.__set_uid(uid);
    elem = reply->element[i + 1];
    rc.__set_timestamp((int32_t)atoi(elem->str));
  }
  freeReplyObject(reply);

  // get redis reply for HGETALL ...
  reply = NULL;
  if (0 != redisGetReply(context, (void**)&reply) || !(reply && reply->type==REDIS_REPLY_ARRAY)) {
    LOG_ERROR("GetContacts error.(hgetall failed.)");
    redis_.ReleaseContext("", context, false);
    return -1;
  }

  LOG_DEBUG("remarked size is : " << reply->elements);
  for (size_t i=0; i<reply->elements; i+=2) {
    BOOST_FOREACH(RecentContact& rc, contacts) {
      int64_t t = 0;
      try {
        t = boost::lexical_cast<int64_t>(reply->element[i]->str);
      } catch (boost::bad_lexical_cast&) {}
      if (rc.uid == t) {
        LOG_DEBUG("remark : " << t << " , " << reply->element[i + 1]->str);
        rc.__set_remark(reply->element[i+1]->str);
        break;
      }
    }
  }
  freeReplyObject(reply);
  redis_.ReleaseContext("", context, true);
  return result;
}

int ContactRedis::Flag(const int64_t uid) {
  return AddContact(uid, _INVALID_UID, 0);
}

int ContactRedis::AddFavorite(const int64_t sender, const int64_t receiver) {
  LOG_INFO("[ContactRedis::AddFavorite] sender=" << sender << "&receiver=" << receiver);
  std::string str = boost::str(boost::format("LPUSH im:contacts:favorite.%1% %2%") % sender % receiver);
  ScopedReply reply(redis_.Execute("", str.c_str()));
  if (!(reply && reply->type==REDIS_REPLY_INTEGER)) {
    LOG_ERROR("add favorite contacts error.(sender=" << sender << ", receiver=" << receiver << ")");
    return -1;
  }
  return 0;
}

int ContactRedis::RemoveFavorite(const int64_t sender, const int64_t receiver) {
  LOG_INFO("[ContactRedis::RemoveFavorite] sender=" << sender << "&receiver=" << receiver);
  std::string str = boost::str(boost::format("LREM im:contacts:favorite.%1% 0 %2%") % sender % receiver);
  ScopedReply reply(redis_.Execute("", str.c_str()));
  if (!(reply && reply->type==REDIS_REPLY_INTEGER)) {
    LOG_ERROR("remove favorite contacts error.(sender=" << sender << ", receiver=" << receiver << ")");
    return -1;
  }
  return 0;
}

int ContactRedis::Remark(const int64_t sender, const int64_t receiver, const std::string& remark) {
  LOG_INFO("[ContactRedis::Remark] sender=" << sender << "&receiver=" << receiver << "&remark=" << remark);
  std::string str;
  if (remark.empty()) {
    str = boost::str(boost::format("HDEL im:contacts:remark.%1% %2%") % sender % receiver);
  } else {
    str = boost::str(boost::format("HSET im:contacts:remark.%1% %2% %3%") % sender % receiver % remark);
  }

  ScopedReply reply(redis_.Execute("", str.c_str()));
  if (!(reply && reply->type==REDIS_REPLY_INTEGER)) {
    LOG_ERROR("remark user(" << sender << " at " << receiver << ") error.");
    return -1;
  }
  return 0;
}

int ContactRedis::GetFavorite(RecentContactList& contacts, const int64_t uid, const int32_t offset, const int32_t count) {
  LOG_INFO("[ContactRedis::GetFavorite] uid=" << uid);
  redisContext *context = redis_.AllocContext("");
  if (!context) {
    LOG_ERROR("get favorites error.(alloc redis context failed)");
    return -1;
  }

  { // create redis command
    std::string lrange = boost::str(boost::format("LRANGE im:contacts:favorite.%1% %2% %3%") % uid % offset % count);
    std::string hgetall= boost::str(boost::format("HGETALL im:contacts:remark.%1%") % uid);
    if (0 != redisAppendCommand(context, lrange.c_str()) || 0 != redisAppendCommand(context, hgetall.c_str())) {
      LOG_ERROR("get favorites error.(append redis command failed) - " << lrange << ";" << hgetall);
      redis_.ReleaseContext("", context, false);
      return -1;
    }
  }

  redisReply *r;
  // get redis reply for LRANGE ...
  r = NULL;
  if (0 != redisGetReply(context, (void**)&r) || !(r && r->type==REDIS_REPLY_ARRAY)) {
    LOG_ERROR("get favorites error.(lrange failed.)");
    redis_.ReleaseContext("", context, false);
    return -1;
  }

  LOG_DEBUG("favorites size is : " << r->elements);
  for (size_t i=0; i<r->elements; ++i) {
    contacts.push_back(RecentContact());
    try {
      contacts.back().__set_uid(boost::lexical_cast<int64_t>(r->element[i]->str));
    } catch(boost::bad_lexical_cast&) {}
  }
  freeReplyObject(r);

  // get redis reply for HGETALL ...
  r = NULL;
  if (0 != redisGetReply(context, (void**)&r) || !(r && r->type==REDIS_REPLY_ARRAY)) {
    LOG_ERROR("get favorites error.(hgetall failed.)");
    redis_.ReleaseContext("", context, false);
    return -1;
  }

  LOG_DEBUG("remarked size is : " << r->elements);
  for (size_t i=0; i<r->elements; i+=2) {
    BOOST_FOREACH(RecentContact& rc, contacts) {
      int64_t t = 0;
      try {
        t = boost::lexical_cast<int64_t>(r->element[i]->str);
      } catch (boost::bad_lexical_cast&) {}
      if (rc.uid == t) {
        LOG_DEBUG("get favorite remark : " << t << " , " << r->element[i + 1]->str);
        rc.__set_remark(r->element[i+1]->str);
        break;
      }
    }
  }
  freeReplyObject(r);
  redis_.ReleaseContext("", context, true);
  return 0;
}

}
